<?php


namespace BeReborn\Database\Pool;


use BeReborn\Base\Component;
use BeReborn\Base\Coroutine;
use BeReborn\Exception\ComponentException;
use BeReborn\Service\Http\Server;
use Exception;
use PDO;
use Swoole\Coroutine\Channel;
use Swoole\Timer;

/**
 * Class Pool
 * @package BeReborn\Database\Pool
 */
abstract class Pool extends Component
{
	/** @var Channel */
	protected $channel;

	protected $_tick = null;

	private $hasCreate = 0;
	public $maxNumber = 50;
	public $minNumber = 20;

	const TICK_MSG = 'Connection Channel tick. Length: ';

	protected $_times = [];

	/**
	 * @param array $config
	 * @return mixed
	 */
	abstract public function createConnect(array $config = []);

	/**
	 * @return mixed
	 */
	abstract public function getConnectConfig();

	abstract public function getReleaseTime();

	abstract public function getPdoCount();

	/**
	 *
	 */
	public function timeTick()
	{
		if ($this->_tick !== null) {
			return;
		}
		$this->_tick = Timer::tick(6000, [$this, 'tickCallback']);
	}

	/**
	 * @return bool|null
	 * @throws Exception
	 */
	public function tickCallback()
	{
		$this->tickMessage();
		if (env('state') == 'exit') {
			Timer::clear($this->_tick);
			return $this->_tick = null;
		}

		if ($this->channel->length() > 0) {
			$times = $this->_times;
			foreach ($times as $key => $time) {
				if (time() - $time < 60) {
					continue;
				}
				$client = $this->pop();
				unset($client);
			}
		}
		return $this->clearTick();
	}

	/**
	 * 心跳监控信息
	 */
	public function tickMessage()
	{
	}

	/**
	 * @return bool|null
	 */
	public function clearTick()
	{
		if ($this->channel->isEmpty()) {
			Timer::clear($this->_tick);
			return $this->_tick = null;
		}
		return true;
	}

	/**
	 * @return int
	 */
	public function getTick()
	{
		return $this->_tick;
	}

	/**
	 * @return mixed
	 * 获取通道长度
	 */
	protected function getChannelLength()
	{
		return (int)$this->channel->length();
	}

	/**
	 * 初始化协程
	 * @throws Exception
	 */
	public function init()
	{
		$app = $this;
		$this->channel = new Channel(50);
		on('CONSOLE_END', function () use ($app) {
			Timer::clearAll();
			$app->channel->close();
		});
	}

	/**
	 * @return void
	 * @throws Exception
	 */
	public function isConnect()
	{
		throw new Exception('You must define connect check callback.');
	}

	/**
	 * @return mixed
	 * @throws Exception
	 * 获取连接，并且标记使用的序列
	 */
	public function getConnect()
	{
		if (!$this->channel->isEmpty()) {
			$pdo = $this->pop();
		} else if ($this->getSum() >= $this->maxNumber) {
			$pdo = $this->pop(30);
		} else {
			return $this->ifNull();
		}
		return $pdo;
	}

	/**
	 * @param int $timeout
	 * @return PDO
	 */
	private function pop($timeout = 0)
	{
		if ($timeout > 0) {
			$pdo = $this->channel->pop($timeout);
		} else {
			$pdo = $this->channel->pop();
		}
		if (!empty($this->_times)) {
			array_shift($this->_times);
		}
		return $pdo;
	}

	/**
	 * @return mixed
	 */
	private function getSum()
	{
		return $this->getPdoCount() + $this->channel->length();
	}


	/**
	 * @return PDO
	 * @throws Exception
	 */
	private function ifNull()
	{
		$config = $this->getConnectConfig();
		if (empty($config) || !is_array($config)) {
			throw new Exception('Db Connect configure must not empty.');
		}
		return $this->createConnect($config);
	}

	/**
	 * @param $number
	 */
	public function createMinClient($number)
	{
		$config = $this->getConnectConfig();
		for ($i = 0; $i < $number; $i++) {
			$this->channel->push($this->createConnect($config));
		}
	}

	/**
	 * @param PDO $connect
	 * @throws
	 */
	public function release(PDO $connect)
	{
		if (!($connect instanceof PDO)) {
			return;
		}
		if ($this->channel->length() >= $this->maxNumber) {
			unset($connect);
		} else {
			try {
				$this->channel->push($connect);
				$this->_times[] = time();
			} catch (\Throwable $throwable) {
				$this->addError($throwable->getMessage());
			}
		}
	}

	/**
	 * 释放链接
	 */
	public function disconnect()
	{
		$this->destroy();
		while ($this->channel->length() > 0) {
			$pdo = $this->channel->pop();
			unset($pdo);
		}
		$this->_times = [];
	}
}
